using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using SlimDX;
using SlimDX.Direct3D9;


namespace Splino
{
    namespace D3D
    {
        /// <summary>
        /// Responsible for managing and drawing all 3D objects in the
        /// scene either user or system created
        /// </summary>
        public class SceneManager
        {
            SplinoObject _masterSceneObject;
            /// <summary>
            /// Gets or sets the root object for the scene, which
            /// handles hierarchical ownership of objects
            /// </summary>
            public SplinoObject MasterSceneObject
            {
                get { return _masterSceneObject; }
                set { _masterSceneObject = value; }
            }

            SplinoObject _tempSceneObject;
            /// <summary>
            /// Gets or sets the root object for holding temporary instances
            /// of objects that may or not become persistant members of the scene
            /// </summary>
            public SplinoObject TempSceneObject
            {
                get { return _tempSceneObject; }
                set { _tempSceneObject = value; }
            }

            List<SplinoObject> _selectedObjects =  new List<SplinoObject>();
            /// <summary>
            /// Gets a list of objects that have been selected
            /// </summary>
            public List<SplinoObject> SelectedObjects
            {
                get { return _selectedObjects; }
            }

            Vector3? _cursorPointLinearConstraintStart;
            public Vector3? CursorPointLinearConstraintStart
            {
                get { return _cursorPointLinearConstraintStart; }
                set { _cursorPointLinearConstraintStart = value; }
            }

            Vector3? _cursorPointLinearConstraintEnd;
            public Vector3? CursorPointLinearConstraintEnd
            {
                get { return _cursorPointLinearConstraintEnd; }
                set { _cursorPointLinearConstraintEnd = value; }
            }

            Vector3 _cursorPoint;
            /// <summary>
            /// Gets or sets the 3D point that corresponds to the position of the cursor.
            /// </summary>
            public Vector3 CursorPoint
            {
                get { return _cursorPoint; }
                set 
                { 
                    _cursorPoint = value;
                    _cursorPointVertexList[0].Position = _cursorPoint;
                    DataStream stream = _cursorPointVertexBuffer.Lock(0, 0, LockFlags.None);
                    stream.WriteRange(_cursorPointVertexList);
                    _cursorPointVertexBuffer.Unlock();
                }
            }

            Vector3 _baseDrawPoint;
            /// <summary>
            /// Gets or sets the 3D point that corresponds to the drawing base point
            /// (ie the point from which grid and angle snapping is measured).
            /// </summary>
            public Vector3 BaseDrawPoint
            {
                get { return _baseDrawPoint; }
                set { _baseDrawPoint = value; }
            }

            bool _baseDrawPointInitialized;
            /// <summary>
            /// Gets or sets whether the 3D point that corresponds to the drawingt base point
            /// has been initialized or not.
            /// </summary>
            public bool BaseDrawPointInitialized
            {
                get { return _baseDrawPointInitialized; }
                set { _baseDrawPointInitialized = value; }
            }

            int[] _splinoObjectTypeCount = 
                new int[Enum.GetValues(typeof(SplinoObjectType)).Length];
            /// <summary>
            /// Gets an array that counts SplinoObject creations by type
            /// for creating unique names for each object
            /// </summary>
            public int[] SplinoObjectTypeCount
            {
                get { return _splinoObjectTypeCount; }
            }

            Hashtable _objectCount = new Hashtable();
            /// <summary>
            /// Gets a Hashtable that counts SplinoObject creations by type
            /// for creating unique names for each object
            /// </summary>
            public Hashtable ObjectCount
            {
                get { return _objectCount; }
            }

            CustomVertex.PositionColored[] _cursorPointVertexList = new CustomVertex.PositionColored[1];
            VertexBuffer _cursorPointVertexBuffer;

            /// <summary>
            /// Vector2 vertex points for the selection rectangle
            /// </summary>
            Vector2[] _selectionRectangleVerts = new Vector2[8];

            Vector2 _selectionStartPoint = new Vector2();
            /// <summary>
            /// Gets or sets a Vector2 representing the starting point for the
            /// selection rectangle
            /// </summary>
            public Vector2 SelectionStartPoint
            {
                get { return _selectionStartPoint; }
                set { _selectionStartPoint = value; }
            }

            Vector2 _selectionEndPoint = new Vector2();
            /// <summary>
            /// Gets or sets a Vector2 representing the end point for the selection
            /// rectangle and adjusts the selection selection rectangle verticies
            /// </summary>
            public Vector2 SelectionEndPoint
            {
                get { return _selectionEndPoint; }
                set 
                { 
                    _selectionEndPoint = value;
                    _selectionRectangleVerts[0] = _selectionStartPoint;
                    _selectionRectangleVerts[1] = new Vector2(_selectionEndPoint.X, _selectionStartPoint.Y);
                    _selectionRectangleVerts[2] = _selectionRectangleVerts[1];
                    _selectionRectangleVerts[3] = _selectionEndPoint;
                    _selectionRectangleVerts[4] = _selectionEndPoint;
                    _selectionRectangleVerts[5] = new Vector2(_selectionStartPoint.X, _selectionEndPoint.Y);
                    _selectionRectangleVerts[6] = _selectionRectangleVerts[5];
                    _selectionRectangleVerts[7] = _selectionStartPoint;

                    if (_selectionStartPoint.X > _selectionEndPoint.X)
                    {
                        _selectionLine.Pattern = -4096;
                        _selectionLine.PatternScale = .5f;
                    }
                    else
                    {
                        _selectionLine.Pattern = -1;
                    }
                }
            }

            /// <summary>
            /// Line that represents the selection rectangle
            /// </summary>
            Line _selectionLine;

            private static readonly object padlock = new object();
            private static volatile SceneManager _instance;
            /// <summary>
            /// The singleton instance for the Scene Manager
            /// </summary>
            public static SceneManager Instance
            {
                get
                {
                    if (_instance == null)
                        lock (padlock)
                        {
                            if (_instance == null)
                                _instance = new SceneManager();
                        }
                    return _instance;
                }
            }
            private SceneManager()
            {
                _masterSceneObject = new SplinoObject();
                _tempSceneObject = new SplinoObject();
                InitializeScene();
            }

            /// <summary>
            /// Lays out the viewport grids and creates the master scene
            /// objects whose children make up all user created Splino Objects
            /// </summary>
            public void InitializeScene()
            {
                DeviceManager.Instance.Device.VertexFormat = CustomVertex.PositionColored.Format;

                foreach (SplinoViewport splinoViewport in DeviceManager.Instance.Viewports)
                {
                    splinoViewport.LayoutGrid();
                    splinoViewport.LayoutAxesOrientationGuide();
                }

                _cursorPointVertexList[0].Color = Palette.PointCursor.ToArgb();
                _cursorPointVertexBuffer = new VertexBuffer(
                    DeviceManager.Instance.Device, 
                    CustomVertex.PositionColored.SizeInBytes*2, 
                    Usage.WriteOnly, 
                    VertexFormat.None, 
                    Pool.Managed);

                _masterSceneObject.ResetBuffers();
                _tempSceneObject.ResetBuffers();

                _selectionLine = new Line(DeviceManager.Instance.Device);
            }

            /// <summary>
            /// Resets all vertex buffers in the scene, including all user
            /// created objects.
            /// </summary>
            public void ResetScene()
            {
                DeviceManager.Instance.Device.VertexFormat = CustomVertex.PositionColored.Format;

                if (_cursorPointVertexBuffer == null || _cursorPointVertexBuffer.Disposed)
                {
                    _cursorPointVertexBuffer = new VertexBuffer(
                        DeviceManager.Instance.Device,
                        CustomVertex.PositionColored.SizeInBytes,
                        Usage.WriteOnly,
                        CustomVertex.PositionColored.Format,
                        Pool.Default);
                }

                _masterSceneObject.ResetBuffers();
                _tempSceneObject.ResetBuffers();

                if (_selectionLine == null || _selectionLine.Disposed)
                    _selectionLine = new Line(DeviceManager.Instance.Device);
            }

            /// <summary>
            /// Disposes every vertex buffer in the scene
            /// </summary>
            public void DisposeScene()
            {
                _cursorPointVertexBuffer.Dispose();

                _masterSceneObject.Dispose();
                _tempSceneObject.Dispose();
            }

            private void DrawCursorPoint()
            {
                DeviceManager.Instance.Device.SetStreamSource(
                    0,
                    _cursorPointVertexBuffer,
                    0,
                    CustomVertex.PositionColored.SizeInBytes);
                DeviceManager.Instance.Device.DrawPrimitives(PrimitiveType.PointList, 0, 1);
            }

            /// <summary>
            /// Draws all user created Splino Objects that are currently unselected and
            /// unlcoked, using normal Z Buffering.
            /// </summary>
            /// <param name="splinoViewport">The viewport being drawn to</param>
            public void DrawSceneLayerSceneObjects(SplinoViewport splinoViewport)
            {
                _masterSceneObject.Draw(DrawLayer.Scene, splinoViewport);

                _tempSceneObject.Draw(DrawLayer.Scene, splinoViewport);
            }

            /// <summary>
            /// Draws all user created Splino Objects that are marked to be drawn on 
            /// the TopMost draw layer, such as selected objects.
            /// </summary>
            /// <param name="splinoViewport">The viewport being drawn to</param>
            public void DrawTopMostSceneObjects(SplinoViewport splinoViewport)
            {
                _masterSceneObject.Draw(DrawLayer.TopMost, splinoViewport);

                if (CommandManager.Instance.ActiveCommandStack.Peek().PointCursorActive)
                    DrawCursorPoint();
            }

            /// <summary>
            /// Draws all user created Splino Objects that are marked to be drawn on
            /// the BottomMost draw layer, such as locked objects.
            /// </summary>
            /// <param name="splinoViewport">The viewport being drawn to</param>
            public void DrawBottomMostSceneObjects(SplinoViewport splinoViewport)
            {
                _masterSceneObject.Draw(DrawLayer.BottomMost, splinoViewport);
            }

            /// <summary>
            /// Creates a new Point Object at the location of the
            /// cursor draw point
            /// </summary>
            /// <returns>A new, unitialized Point Object</returns>
            public PointObject Command_Point_MouseDown()
            {
                PointObject newPoint = new PointObject();
                newPoint.Center = _cursorPoint;
                return newPoint;
            }

            /// <summary>
            /// Creates a new Point Object at the specified coordinates
            /// </summary>
            /// <param name="input">World coordinates of the Point
            /// Object's location</param>
            /// <returns>A new, unitialized Point Object</returns>
            public PointObject Command_Point_Input(Vector3 input)
            {
                PointObject newPoint = new PointObject();
                newPoint.Center = input;
                return newPoint;
            }

            /// <summary>
            /// Displays a form listing every user created Splino 
            /// Object in the scene
            /// </summary>
            public void ShowSceneManagerTree()
            {
                _masterSceneObject.ShowPropertiesWindow();
            }

            /// <summary>
            /// Draws a user interactive rectangle on the viewport representing
            /// an area that the user wishes to select objects from
            /// </summary>
            public void DrawSelectionRectangle()
            {
                _selectionLine.Begin();
                _selectionLine.Draw(_selectionRectangleVerts, Splino.Palette.selectionRectangle);
                _selectionLine.End();

                DeviceManager.Instance.RequestRender();
            }

            /// <summary>
            /// Deselects all selected Splino Objects
            /// </summary>
            public void ClearSelectedItems()
            {
                List<SplinoObject> clearList = new List<SplinoObject>();
                foreach (SplinoObject splinoObject in _selectedObjects)
                    clearList.Add(splinoObject);
                foreach (SplinoObject splinoObject in clearList)
                    splinoObject.Deselect();
                _selectedObjects.Clear();
            }

            /// <summary>
            /// Deletes all user created Splino Objects
            /// </summary>
            public void ClearScene()
            {
                List<SplinoObject> deletionList = new List<SplinoObject>();
                foreach (SplinoObject splinoObject in _masterSceneObject.SceneChildren)
                    deletionList.Add(splinoObject);
                foreach (SplinoObject splinoObject in deletionList)
                    splinoObject.Delete();
            }

            /// <summary>
            /// Deselects every selected object, and selects every other object
            /// </summary>
            public void InvertSelection()
            {
                foreach (SplinoObject splinoObject in _masterSceneObject.SceneChildren)
                {
                    if (splinoObject.Selected)
                        splinoObject.Deselect();
                    else
                        splinoObject.Select();
                }
            }

            /// <summary>
            /// Reinitializes the SceneManager
            /// </summary>
            public void ClearSceneManager()
            {
                _instance = null;
            }

            /// <summary>
            /// Checks to see if the name of the Splino Object already exists
            /// in the scene and appends an underscore and a counter if it does.
            /// </summary>
            /// <param name="splinoObject">A Splino Object wbose name is checked
            /// to make sure there are no duplicates</param>
            public void FixDuplicateName(SplinoObject splinoObject)
            {
                FixDuplicateName(splinoObject, _masterSceneObject);
            }

            private void FixDuplicateName(SplinoObject splinoObject, SplinoObject parentObject)
            {
                bool nameFound = false;

                foreach (SplinoObject sceneObject in parentObject.SceneChildren)
                    if (splinoObject.Name == sceneObject.Name)
                        nameFound = true;

                int i = 0;
                while (nameFound)
                {
                    nameFound = false;
                    i++;
                    foreach (SplinoObject sceneObject2 in parentObject.SceneChildren)
                    {
                        if ((splinoObject.BaseName + "_" + i) ==
                            sceneObject2.Name)
                            nameFound = true;
                    }
                }
                if (i > 0)
                    splinoObject.Name = splinoObject.BaseName + "_" + i;

                foreach (SplinoObject childObject in parentObject.SceneChildren)
                    FixDuplicateName(splinoObject, childObject);
            }

            /// <summary>
            /// Returns true if an object in the scene hierarchy has the same name
            /// as the parameter object's name.
            /// </summary>
            /// <param name="objectName">The name of the object that will be checked
            /// for duplicates.</param>
            /// <returns>True if an object with the same name as the parameter
            /// object's name was found.</returns>
            public bool CheckForDuplicateName(String objectName)
            {
                return CheckForDuplicateName(objectName, _masterSceneObject);
            }

            private bool CheckForDuplicateName(String objectName, SplinoObject parentObject)
            {
                foreach (SplinoObject sceneObject in parentObject.SceneChildren)
                    if (objectName == sceneObject.Name)
                        return true;

                foreach (SplinoObject sceneObject in parentObject.SceneChildren)
                    if (CheckForDuplicateName(objectName, sceneObject))
                        return true;

                return false;
            }

            /// <summary>
            /// Searches all scene objects for a Splino Object with a matching name
            /// as the String parameter, and returns that object if it was found, or
            /// null if it was not.
            /// </summary>
            /// <param name="objectName">The name of the Splino Object being searched 
            /// for</param>
            /// <returns>The Splino Object with the matching name, if it was found.
            /// Null if it was not found.</returns>
            public SplinoObject GetSplinoObjectByName(String objectName)
            {
                if (objectName == "")
                    return _masterSceneObject;

                return GetSplinoObjectByName(objectName, _masterSceneObject);
            }
            private SplinoObject GetSplinoObjectByName(String objectName, SplinoObject parentObject)
            {
                if (parentObject.Name.ToLower() == objectName.ToLower())
                    return parentObject;

                foreach (SplinoObject childObject in parentObject.SceneChildren)
                    if (GetSplinoObjectByName(objectName, childObject) != null)
                        return childObject;

                return null;
            }

            /// <summary>Event fired when a Splino object is initialized.</summary>
            public event SplinoObjectInitializedEventHandler SplinoObjectInitialized;

            /// <summary>
            /// Causes the Scene Manager to fire the SplinoObjectInitialized event
            /// with the given Splino object in the event arguments.
            /// </summary>
            /// <param name="splinoObject">The Splino object that was initialized.</param>
            public void InitializedSplinoObject(SplinoObject splinoObject)
            {
                if (SplinoObjectInitialized != null)
                    SplinoObjectInitialized(
                        this,
                        new SplinoObjectEventArgs(splinoObject));
            }

            /// <summary>Event fired when a Splino Object is locked.</summary>
            public event SplinoObjectLockedEventHandler SplinoObjectLocked;

            /// <summary>
            /// Causes the Scene Manager to fire the SplinoObjectLocked event
            /// with the given Splino object in the event arguments.,
            /// </summary>
            /// <param name="splinoObject">The Splino object that was locked.</param>
            public void LockedSplinoObject(SplinoObject splinoObject)
            {
                if (SplinoObjectLocked != null)
                    SplinoObjectLocked(
                        this,
                        new SplinoObjectEventArgs(splinoObject));
            }

            /// <summary>Event fired when a Splino Object is unlocked.</summary>
            public event SplinoObjectUnlockedEventHandler SplinoObjectUnlocked;

            /// <summary>
            /// Causes the Scene Manager to fire the SplinoObjectLocked event
            /// with the given Splino object in the event arguments.,
            /// </summary>
            /// <param name="splinoObject">The Splino object that was locked.</param>
            public void UnlockedSplinoObject(SplinoObject splinoObject)
            {
                if (SplinoObjectUnlocked != null)
                    SplinoObjectUnlocked(
                        this,
                        new SplinoObjectEventArgs(splinoObject));
            }

            /// <summary>Event fired when a Splino Object is hidden.</summary>
            public event SplinoObjectHiddenEventHandler SplinoObjectHidden;

            /// <summary>
            /// Causes the Scene Manager to fire the SplinoObjectHidden event
            /// with the given Splino object in the event arguments.
            /// </summary>
            /// <param name="splinoObject">The Splino object that was hidden.</param>
            public void HiddenSplinoObject(SplinoObject splinoObject)
            {
                if (SplinoObjectHidden != null)
                    SplinoObjectHidden(
                        this,
                        new SplinoObjectEventArgs(splinoObject));
            }

            /// <summary>Event fired when a Splino Object is shown.</summary>
            public event SplinoObjectShownEventHandler SplinoObjectShown;

            /// <summary>
            /// Causes the Scene Manager to fire the SplinoObjectShown event
            /// when the given Splino object in the event arguments.
            /// </summary>
            /// <param name="splinoObject">The Splino object that shown.</param>
            public void ShownSplinoObject(SplinoObject splinoObject)
            {
                if (SplinoObjectShown != null)
                    SplinoObjectShown(
                        this,
                        new SplinoObjectEventArgs(splinoObject));
            }

            /// <summary>Event fired when a Splino Object's display color is changed.</summary>
            public event SplinoObjectColorChangeEventHandler SplinoObjectColorChange;

            /// <summary>
            /// Causes the Scene Manager to fire the SplinoObjectColorChange event
            /// when the given Splino Object's display color is changed.
            /// </summary>
            /// <param name="splinoObject">The Splino Object that had its display color changed.</param>
            public void ColorChangedSplinoObject(SplinoObject splinoObject)
            {
                if (SplinoObjectColorChange != null)
                    SplinoObjectColorChange(
                        this,
                        new SplinoObjectEventArgs(splinoObject));
            }

            /// <summary>Event fired when a Splino Object is selected.</summary>
            public event SplinoObjectSelectedEventHandler SplinoObjectSelected;

            /// <summary>
            /// Causes the Scene Manager to fire the SplinoObjectSelected event
            /// when the given Splino Object is selected.
            /// </summary>
            /// <param name="splinoObject">The selected Splino Object</param>
            public void SelectedSplinoObject(SplinoObject splinoObject)
            {
                if (SplinoObjectSelected != null)
                    SplinoObjectSelected(
                        this,
                        new SplinoObjectEventArgs(splinoObject));
            }

            /// <summary>Event fired when a Splino Object is deselected.</summary>
            public event SplinoObjectDeselectedEventHandler SplinoObjectDeselected;

            /// <summary>
            /// Causes the Scene Manager to fire the SplinoObjectDeselected event
            /// when the given Splino Object is deselected.
            /// </summary>
            /// <param name="splinoObject"></param>
            public void DeselectedSplinoObject(SplinoObject splinoObject)
            {
                if (SplinoObjectDeselected != null)
                    SplinoObjectDeselected(
                        this,
                        new SplinoObjectEventArgs(splinoObject));
            }
        }

        /// <summary>
        /// Event arguments containing the Splino object that triggered the event
        /// </summary>
        public class SplinoObjectEventArgs : EventArgs
        {
            private readonly SplinoObject _splinoObject;

            /// <summary>
            /// The Splino Object that triggered the event
            /// </summary>
            public SplinoObject SplinoObject { get { return _splinoObject; } }

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="splinoObject">The Splino object that triggered the event</param>
            public SplinoObjectEventArgs(SplinoObject splinoObject)
            {
                _splinoObject = splinoObject;
            }
        }

        /// <summary>
        /// Splino object initialized event handler
        /// </summary>
        /// <param name="sender">The Scene Manager</param>
        /// <param name="e">Splino Object event arguments</param>
        public delegate void SplinoObjectInitializedEventHandler(object sender, SplinoObjectEventArgs e);

        /// <summary>
        /// Splino object lcoked event handler
        /// </summary>
        /// <param name="sender">Scene Manager</param>
        /// <param name="e">Splino Object event arguments</param>
        public delegate void SplinoObjectLockedEventHandler(object sender, SplinoObjectEventArgs e);

        /// <summary>
        /// Splino object unlocked event handler
        /// </summary>
        /// <param name="sender">The Scene Manager</param>
        /// <param name="e">Splino Object event arguments</param>
        public delegate void SplinoObjectUnlockedEventHandler(object sender, SplinoObjectEventArgs e);

        /// <summary>
        /// Splino object hidden event handler
        /// </summary>
        /// <param name="sender">The Scene Manager</param>
        /// <param name="e">Spliino Object event arguments</param>
        public delegate void SplinoObjectHiddenEventHandler(object sender, SplinoObjectEventArgs e);

        /// <summary>
        /// Splino object shown event handler
        /// </summary>
        /// <param name="sender">The Scene Manager</param>
        /// <param name="e">Splino Object event arguments</param>
        public delegate void SplinoObjectShownEventHandler(object sender, SplinoObjectEventArgs e);

        /// <summary>
        /// Splino object color change event handler
        /// </summary>
        /// <param name="sender">The Scene Manager</param>
        /// <param name="e">Splino Object event arguments</param>
        public delegate void SplinoObjectColorChangeEventHandler(object sender, SplinoObjectEventArgs e);

        /// <summary>
        /// Splino object selected event handler
        /// </summary>
        /// <param name="sender">The Scene Manager</param>
        /// <param name="e">Splino Object event arguments</param>
        public delegate void SplinoObjectSelectedEventHandler(object sender, SplinoObjectEventArgs e);

        /// <summary>
        /// Splino object deselected event handler
        /// </summary>
        /// <param name="sender">The Scene Manager</param>
        /// <param name="e"></param>
        public delegate void SplinoObjectDeselectedEventHandler(object sender, SplinoObjectEventArgs e);
    }
}
